/////////////////////////////////////////////////////////////////////////////
// Name:        PackagesListCtrl.cpp
// Purpose:
// Author:      yospaly
// Modified by:
// Created:     11/12/2008 21:42:32
// RCS-ID:
// Copyright:   Copyright (C) 2008, yospaly
// Licence:
/////////////////////////////////////////////////////////////////////////////

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif

////@begin includes
////@end includes
#include <wx/utils.h>
#include <wx/menu.h>

#include "PackagesListCtrl.h"
#include "events.h"
#include "inibuilder.h"
#include "app_resources.h"
#include "common.h"

////@begin XPM images
////@end XPM images
#include "../res/16x16/package-available.xpm"
#include "../res/16x16/package-installed-updated.xpm"
#include "../res/16x16/package-installed-outdated.xpm"

#include "../res/16x16/package-install.xpm"
#include "../res/16x16/package-upgrade.xpm"
#include "../res/16x16/package-remove.xpm"
#include "../res/16x16/package-purge.xpm"
#include "../res/16x16/package-reinstall.xpm"

/*!
 * PackagesListCtrl type definition
 */

IMPLEMENT_DYNAMIC_CLASS( PackagesListCtrl, wxListCtrl )


/*!
 * PackagesListCtrl event table definition
 */

BEGIN_EVENT_TABLE( PackagesListCtrl, wxListCtrl )

////@begin PackagesListCtrl event table entries
    EVT_LIST_ITEM_SELECTED( ID_PACKAGES_LISTCTRL, PackagesListCtrl::OnSelected )
    EVT_LIST_ITEM_RIGHT_CLICK( ID_PACKAGES_LISTCTRL, PackagesListCtrl::OnRightClick )

////@end PackagesListCtrl event table entries

    EVT_COMMAND(ID_PACKAGES_LISTCTRL, wxEVT_PKG_FILTER_EVENT, PackagesListCtrl::OnPkgFilter)
    EVT_COMMAND(ID_PACKAGES_LISTCTRL, wxEVT_PKGSRC_FILTER_EVENT, PackagesListCtrl::OnPkgSrcFilter)
    EVT_MENU(ID_UNMARK_MENUITEM, PackagesListCtrl::OnUnmarkMenuClick)
    EVT_MENU(ID_INSTALL_MENUITEM, PackagesListCtrl::OnInstallMenuClick)
	EVT_MENU(ID_REINSTALL_MENUITEM, PackagesListCtrl::OnReinstallMenuClick)
	EVT_MENU(ID_UPGRADE_MENUITEM, PackagesListCtrl::OnUpgradeMenuClick)
	EVT_MENU(ID_REMOVAL_MENUITEM, PackagesListCtrl::OnRemoveMenuClick)
END_EVENT_TABLE()


/*!
 * PackagesListCtrl constructors
 */

PackagesListCtrl::PackagesListCtrl()
{
    Init();
}

PackagesListCtrl::PackagesListCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxValidator& validator)
{
    Init();
    Create(parent, id, pos, size, style, validator);
}


/*!
 * PackagesListCtrl creator
 */

bool PackagesListCtrl::Create(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxValidator& validator)
{
////@begin PackagesListCtrl creation
    wxListCtrl::Create(parent, id, pos, size, style, validator);
    CreateControls();
////@end PackagesListCtrl creation
    return true;
}


/*!
 * PackagesListCtrl destructor
 */

PackagesListCtrl::~PackagesListCtrl()
{
////@begin PackagesListCtrl destruction
////@end PackagesListCtrl destruction
    EventCenter::unregister(wxEVT_PKG_FILTER_EVENT, this);
    EventCenter::unregister(wxEVT_PKGSRC_FILTER_EVENT, this);
}


/*!
 * Member initialisation
 */

void PackagesListCtrl::Init()
{
////@begin PackagesListCtrl member initialisation
////@end PackagesListCtrl member initialisation
}


/*!
 * Control creation for PackagesListCtrl
 */

void PackagesListCtrl::CreateControls()
{
////@begin PackagesListCtrl content construction
    this->SetFont(wxFont(wxNORMAL_FONT->GetPointSize(), wxSWISS, wxNORMAL, wxNORMAL, false, wxT("Verdana")));
////@end PackagesListCtrl content construction
    
    wxBitmap available_xpm(package_available_xpm);
    wxBitmap updated_xpm(package_installed_updated_xpm);
    wxBitmap outdated_xpm(package_installed_outdated_xpm);
    
    wxBitmap install_xpm(package_install_xpm);
    wxBitmap reinstall_xpm(package_reinstall_xpm);
    wxBitmap upgrade_xpm(package_upgrade_xpm);
    wxBitmap remove_xpm(package_remove_xpm);
    wxBitmap purge_xpm(package_purge_xpm);
    // wxBitmap new_xpm(package_new_xpm);

    p_img_list = new wxImageList(16,16);
    p_img_list->Add(available_xpm);
    p_img_list->Add(updated_xpm);
    p_img_list->Add(outdated_xpm);
    
    p_img_list->Add(install_xpm);
    p_img_list->Add(reinstall_xpm);
    p_img_list->Add(upgrade_xpm);
    p_img_list->Add(remove_xpm);
    p_img_list->Add(purge_xpm);

    AssignImageList(p_img_list, wxIMAGE_LIST_SMALL);

	long index = 0;
    index = this->InsertColumn(index, _("Description"));
    index = this->InsertColumn(index, _("Size"));
    index = this->InsertColumn(index, _("Latest Version"));
    index = this->InsertColumn(index, _("Installed Version"));
    index = this->InsertColumn(index, _("Package"));
    
    EventCenter::registerr(wxEVT_PKG_FILTER_EVENT, this);
    EventCenter::registerr(wxEVT_PKGSRC_FILTER_EVENT, this);
}

static void _filter_package(PkgInfo::STATUS status, wxArrayString& pkgs) {
	const wxArrayString& pkg_names = IniBuilder::get().pkg_names;
    
	for (int i=0; i<pkg_names.GetCount(); i++) {
		PkgInfo::STATUS status2 = IniBuilder::get().get_pkg_status(pkg_names[i]);
		if (status==status2) {
			pkgs.Add(pkg_names[i]);
		}
	}
}
void PackagesListCtrl::ShowPkgs(const wxString& cat, bool show_source) {
    int row = 0;
    int col = 0;
    this->DeleteAllItems();
    
    IniBuilder& inibuilder = IniBuilder::get();
	
	// InstalledBuilder& installed_builder = InstalledBuilder::get();
	
	wxArrayString* p_pkg_names = NULL;
	wxArrayString tmp_pkg_names;
    if (cat==_T("ALL")) {
    	p_pkg_names = &inibuilder.pkg_names;
    } else if (cat==_T("Available")) {
    	_filter_package(PkgInfo::AVAILABLE, tmp_pkg_names);
    	p_pkg_names = &tmp_pkg_names;
    } else if (cat==_T("Updated")) {
    	_filter_package(PkgInfo::UPDATED, tmp_pkg_names);
    	p_pkg_names = &tmp_pkg_names;
    } else if (cat==_T("Outdated")) {
    	_filter_package(PkgInfo::OUTDATED, tmp_pkg_names);
    	p_pkg_names = &tmp_pkg_names;
    } else if (cat==_T("Mark for Install")) {
    	_filter_package(PkgInfo::XINSTALL, tmp_pkg_names);
    	p_pkg_names = &tmp_pkg_names;
    } else if (cat==_T("Mark for Reinstall")) {
    	_filter_package(PkgInfo::REINSTALL, tmp_pkg_names);
    	p_pkg_names = &tmp_pkg_names;
    } else if (cat==_T("Mark for Upgrade")) {
    	_filter_package(PkgInfo::UPGRADE, tmp_pkg_names);
    	p_pkg_names = &tmp_pkg_names;
    } else if (cat==_T("Mark for Remove")) {
    	_filter_package(PkgInfo::REMOVE, tmp_pkg_names);
    	p_pkg_names = &tmp_pkg_names;
    } else {
    	p_pkg_names = &inibuilder.categories[cat].pkg_names;
    }
    const wxArrayString& pkg_names = *p_pkg_names;
	

    for( int i=0; i<pkg_names.GetCount(); i++ ) {
    	PkgInfo& pkg_info = inibuilder.packages[pkg_names[i]];
    	
    	col = 0;
    	
    	/* TODO: 
    	if (show_source) {
        	if (pkg_info.source_path.IsEmpty() ) {
        		continue;
        	}
        	
        	int flag = inibuilder.is_installed(pkg_info.name);
        	
        	if ( InstalledBuilder::SOURCE_UPGRADABLE & flag ) { // installed and upgradable
            	this->InsertItem(row, wxString::Format(_T("[src] %s"), pkg_info.name.c_str()), PkgInfo::OUTDATED);
            	this->SetItem(row, ++col, installed_builder.get_version(pkg_info.name));
        	} else if ( InstalledBuilder::SOURCE_INSTALLED & flag ) { // installed
            	this->InsertItem(row, wxString::Format(_T("[src] %s"), pkg_info.name.c_str()), PkgInfo::UPDATED);
            	this->SetItem(row, ++col, installed_builder.get_version(pkg_info.name));
        	} else { // not installed
            	this->InsertItem(row, wxString::Format(_T("[src] %s"), pkg_info.name.c_str()), PkgInfo::AVAILABLE);
            	this->SetItem(row, ++col, wxString());
        	}
    	} else */{
    		/*
			if ( InstalledBuilder::BINARY_UPGRADABLE & installed_builder.is_installed(pkg_info.name) ) {
				this->InsertItem(row, pkg_info.name, PkgInfo::OUTDATED); // installed and upgradable
				this->SetItem(row, ++col, installed_builder.get_version(pkg_info.name));
			} else if ( InstalledBuilder::BINARY_INSTALLED & installed_builder.is_installed(pkg_info.name) ) {
				this->InsertItem(row, pkg_info.name, PkgInfo::UPDATED); // installed
				this->SetItem(row, ++col, installed_builder.get_version(pkg_info.name));
			} else {
				this->InsertItem(row, pkg_info.name, PkgInfo::AVAILABLE); // available
				this->SetItem(row, ++col, wxString());
			} */
    		
    		PkgInfo::STATUS status = inibuilder.get_pkg_status(pkg_info.name);
			this->InsertItem(row, pkg_info.name, status); // installed
			this->SetItem(row, ++col, inibuilder.get_installed_version(pkg_info.name));
    	}
    	
    	this->SetItem(row, ++col, pkg_info.version);
    	this->SetItem(row, ++col, format_size_str(pkg_info.binary_size));
    	this->SetItem(row, ++col, pkg_info.sdesc);
    	
    	row++;
    }

    for (int i=0; i<this->GetColumnCount(); i++) {
    	this->SetColumnWidth(i, wxLIST_AUTOSIZE);
    }
}

	
void PackagesListCtrl::OnPkgFilter(wxCommandEvent &event) {	
	_cat = event.GetString();
    this->ShowPkgs(_cat);
}


void PackagesListCtrl::OnPkgSrcFilter(wxCommandEvent &event) {
    const wxString& cat = event.GetString();
    this->ShowPkgs(cat, true);
}

/*!
 * wxEVT_COMMAND_LIST_ITEM_SELECTED event handler for ID_PACKAGES_LISTCTRL
 */

void PackagesListCtrl::OnSelected( wxListEvent& event )
{
	_pkgname = event.GetItem().GetText();
	_pkgname.Replace(_T(" [src]"), _T(""), false);
	
    EventCenter::post(wxEVT_PKG_PROPERTIES_EVENT, _pkgname);
}


/*!
 * wxEVT_COMMAND_LIST_ITEM_RIGHT_CLICK event handler for ID_PACKAGES_LISTCTRL
 */

void PackagesListCtrl::OnRightClick( wxListEvent& event )
{
	// const wxListItem& item = event.GetItem();
	// int index = item.GetImage();
	
	PkgInfo::STATUS status = IniBuilder::get().get_pkg_status(_pkgname);
	
	//	wxMessageBox(wxString::Format(_T("%d"), _selected_image));
	
	wxMenu* p_menu = AppResources::CreatePkgOpMenu();
	// wxMenuItem* p_unmark = p_menu->FindItem(ID_UNMARK_MENUITEM);
	p_menu->Destroy(ID_UNMARK_MENUITEM); // TODO: future
	wxMenuItem* p_install = p_menu->FindItem(ID_INSTALL_MENUITEM);
	wxMenuItem* p_reinstall = p_menu->FindItem(ID_REINSTALL_MENUITEM);
	wxMenuItem* p_upgrade = p_menu->FindItem(ID_UPGRADE_MENUITEM);
	wxMenuItem* p_removal = p_menu->FindItem(ID_REMOVAL_MENUITEM);
	// wxMenuItem* p_complete_removal = p_menu->FindItem(ID_COMPLETE_REMOVAL_MENUITEM); // TODO: future
	p_menu->Destroy(ID_COMPLETE_REMOVAL_MENUITEM);

	
	// marked before ?
	if ( _mark4hash.find(_pkgname) == _mark4hash.end() ) {
		//p_unmark->Enable(false);
	}
	
	switch (status) {
	case PkgInfo::AVAILABLE :
		p_install->Enable(true);
		p_reinstall->Enable(false);
		p_upgrade->Enable(false);
		p_removal->Enable(false);
		// p_complete_removal->Enable(false);
		break;
	case PkgInfo::UPDATED :
		p_install->Enable(false);
		p_reinstall->Enable(true);
		p_upgrade->Enable(false);
		p_removal->Enable(true);
		// p_complete_removal->Enable(true);
		break;
	case PkgInfo::OUTDATED :
		p_install->Enable(false);
		p_reinstall->Enable(false);
		p_upgrade->Enable(true);
		p_removal->Enable(true);
//		p_complete_removal->Enable(true);
		break;
	case PkgInfo::XINSTALL :
		p_install->Enable(false);
		p_reinstall->Enable(false);
		p_upgrade->Enable(false);
		p_removal->Enable(false);
//		p_complete_removal->Enable(true);
		break;
	default :
		p_install->Enable(false);
		p_reinstall->Enable(false);
		p_upgrade->Enable(false);
		p_removal->Enable(false);
//		p_complete_removal->Enable(true);
		break;
	}
	
	this->PopupMenu(p_menu);
}


bool PackagesListCtrl::_mark_for(const wxString& pkg, PkgInfo::STATUS status) {
	PkgInfo::STATUS i = IniBuilder::get().get_pkg_status(pkg);
	
	// some package may have dependence circle, ie: bash package
	if (i>=PkgInfo::XINSTALL) {
		return false;
	}
	
	_mark4hash[pkg] = i;
	IniBuilder::get().set_pkg_status(pkg, status);
	
	return true;
}

bool PackagesListCtrl::_unmark(const wxString& pkg) {
	IntegerHash::iterator it = _mark4hash.find(pkg);
	
	if ( it==_mark4hash.end() ) {
		return false;
	}
	IniBuilder::get().set_pkg_status(pkg, PkgInfo::STATUS(it->second));
	
	_mark4hash.erase(pkg); // = -1;
	return true;
}

void PackagesListCtrl::OnUnmarkMenuClick(wxCommandEvent &event) {
	// TODO: future  _unmark(_pkgname);
}

void PackagesListCtrl::OnInstallMenuClick(wxCommandEvent &event) {
	const wxArrayString& requires = IniBuilder::get().get_pkg_all_requires(_pkgname);
	wxArrayString install;
	
	wxString x(wxString::Format(
					_("Following packages required by package \"%s\", will be also installed :\n"),
					_pkgname.c_str() )
			);
	wxString r;
	
	int j = 0;
	int k = 0;
	for (int i=0; i<requires.GetCount(); i++) {
		switch ( IniBuilder::get().get_pkg_status(requires[i]) ) {
		case PkgInfo::AVAILABLE :
			if (j%8==0) {
				x.Append(_T("\n"));
			} else {
				x.Append(_T(", "));
			}
			x.Append(requires[i]);
			install.Add(requires[i]);
			j++;
			break;
		case PkgInfo::REMOVE :
			if (k%8==0) {
				r.Append(_T("\n"));
			} else {
				r.Append(_T(", "));
			}
			r.Append(requires[i]);
			k++;
			break;
		}
	}
	
	if ( false==r.IsEmpty() ) { // conflict
		r.Prepend(wxString::Format(
					_("Following packages required by package \"%s\", but marked to be removed :\n"),
					_pkgname.c_str() )
			);
		wxMessageBox(r, _("Conflict"), wxOK|wxICON_ERROR);
		return;
	}
	
	if ( true==install.IsEmpty() ) {
		_mark_for(_pkgname, PkgInfo::XINSTALL);
		_refresh();
		return;
	}
	
	x.Append(_T("\n\n"));
	x.Append(_("Mark these packages to be installed ?"));
	
	if ( wxOK==wxMessageBox(x, _("Confirm"), wxOK|wxCANCEL) ) {
		_mark_for(_pkgname, PkgInfo::XINSTALL);
		
		for (int i=0; i<install.GetCount(); i++) {
			_mark_for(install[i], PkgInfo::XINSTALL);
		}
	}
	
	_refresh();
}

void PackagesListCtrl::OnReinstallMenuClick(wxCommandEvent &event) {
	_mark_for(_pkgname, PkgInfo::REINSTALL);
	_refresh();
}

void PackagesListCtrl::OnUpgradeMenuClick(wxCommandEvent &event) {
	const wxArrayString& requires = IniBuilder::get().get_pkg_all_requires(_pkgname);
	
	wxString x(wxString::Format(
					_("Following packages required by package \"%s\", will be also installed :\n"),
					_pkgname.c_str() )
			);
	wxString r;
	wxArrayString install;
	
	int j = 0;
	int k = 0;
	for (int i=0; i<requires.GetCount(); i++) {
		switch ( IniBuilder::get().get_pkg_status(requires[i]) ) {
		case PkgInfo::AVAILABLE :
			if (j%8==0) {
				x.Append(_T("\n"));
			} else {
				x.Append(_T(", "));
			}
			x.Append(requires[i]);
			install.Add(requires[i]);
			j++;
			break;
		case PkgInfo::REMOVE :
			if (k%8==0) {
				r.Append(_T("\n"));
			} else {
				r.Append(_T(", "));
			}
			r.Append(requires[i]);
			k++;
			break;
		}
	}
	
	if ( false==r.IsEmpty() ) { // conflict
		r.Prepend(wxString::Format(
					_("Following packages required by package \"%s\", but marked to be removed :\n"),
					_pkgname.c_str() )
			);
		wxMessageBox(r, _("Conflict"), wxOK|wxICON_ERROR);
		return;
	}
	
	if ( true==install.IsEmpty() ) {
		_mark_for(_pkgname, PkgInfo::UPGRADE);
		_refresh();
		return;
	}
	
	x.Append(_T("\n\n"));
	x.Append(_("Mark these packages to be installed ?"));
	
	if ( wxOK==wxMessageBox(x, _("Confirm"), wxOK|wxCANCEL) ) {
		_mark_for(_pkgname, PkgInfo::UPGRADE);
		
		for (int i=0; i<install.GetCount(); i++) {
			_mark_for(install[i], PkgInfo::XINSTALL);
		}
	}
	
	_refresh();
}

void PackagesListCtrl::OnRemoveMenuClick(wxCommandEvent &event) {
	if ( IniBuilder::get().is_base_pkg(_pkgname) ) {
		wxMessageBox(_("You can't remove \"Base\" category packages"), _("Sorry"), wxOK|wxICON_ERROR);
		return;
	}
	
	const wxArrayString& dependents = IniBuilder::get().get_pkg_all_dependents(_pkgname);
	
	if ( true==dependents.IsEmpty() ) {
		_mark_for(_pkgname, PkgInfo::REMOVE);
		_refresh();
		return;
	}
	
	wxArrayString remove;
	wxString r;
	
	wxString x(wxString::Format(
				_("Following packages require package \"%s\", will be also removed :\n"),
				_pkgname.c_str() )
		);
	
	int k=0;
	int j=0;
	for (int i=0; i<dependents.GetCount(); i++) {
		switch ( IniBuilder::get().get_pkg_status(dependents[i]) ) {
		case PkgInfo::UPDATED :
		case PkgInfo::OUTDATED :
			if (j%8==0) {
				x.Append(_T("\n"));
			} else {
				x.Append(_T(", "));
			}
			x.Append(dependents[i]);
			remove.Add(dependents[i]);
			j++;
			break;
		case PkgInfo::XINSTALL :
		case PkgInfo::UPGRADE :
			if (k%8==0) {
				r.Append(_T("\n"));
			} else {
				r.Append(_T(", "));
			}
			r.Append(dependents[i]);
			k++;
			break;
		}
	}

	if ( false==r.IsEmpty() ) { // conflict
		r.Prepend(wxString::Format(
					_("Following packages required by package \"%s\", "
					"but marked to be installed or upgraded :\n"),
					_pkgname.c_str() )
			);
		wxMessageBox(r, _("Conflict!"), wxOK|wxICON_ERROR);
		return;
	}
	
	if ( true==remove.IsEmpty() ) {
		_mark_for(_pkgname, PkgInfo::REMOVE);
		_refresh();
		return;
	}
	
	x.Append(_T("\n\n"));
	x.Append(_("Mark all these installed packages to be removed ?"));
	
	if ( wxOK==wxMessageBox(x, _("Confirm"), wxOK|wxCANCEL) ) {
		_mark_for(_pkgname, PkgInfo::REMOVE);
		
		for (int i=0; i<remove.GetCount(); i++) {
			_mark_for(remove[i], PkgInfo::REMOVE);
		}
	}
	
	_refresh();
}

bool PackagesListCtrl::reload() {
	IntegerHash::iterator it;
	
	for ( it=_mark4hash.begin(); it!=_mark4hash.end(); ++it ) {
		IniBuilder::get().set_pkg_status(it->first, PkgInfo::STATUS(it->second));
	}
	
	_mark4hash.clear();
	this->ShowPkgs(_cat);
}

bool PackagesListCtrl::_refresh() {
	this->ShowPkgs(_cat);
	long i = this->FindItem(0,_pkgname);
	return this->EnsureVisible(i);
}
